home *** CD-ROM | disk | FTP | other *** search
/ Multimedia Jumpstart / Multimedia Microsoft Jumpstart Version 1.1a (Microsoft).BIN / develpmt / drivers / mscdex / audio / audio.c next >
Encoding:
C/C++ Source or Header  |  1990-10-15  |  15.5 KB  |  593 lines

  1. /* audio.c -
  2. **
  3. **    Sample program that reads TOC from CD and asks CDROM
  4. **    drive to play contents of audio disc.
  5. **    Plays only from the first CDROM drive that it finds.
  6. **
  7. **    Companion documentation:
  8. **        - MSCDEX Function Requests
  9. **          For finding device drivers from MSCDEX
  10. **        - CDROM Device Driver Specification
  11. **          For communicating with the physical device
  12. **        - MSDOS Programmers Reference
  13. **          Documentation on device drivers and device driver layout
  14. **
  15. **  HISTORY:
  16. **      10/01/90 Final (v1.0) JohnYG 
  17. */
  18.  
  19. #include <stdio.h>
  20. #include <ctype.h>
  21. #include <dos.h>
  22. #include <process.h>
  23.  
  24.     /* If WHOLE is defined, play entire disc.
  25.     ** If not defined, play about 10 seconds from each audio track
  26.     */
  27. #define WHOLE 1
  28.  
  29.     /* Macros for building/taking apart long's */
  30. #define MAKELONG(lo, hi)  ((long)(((unsigned)(lo)) | ((unsigned long)((unsigned)(hi))) << 16))
  31. #define LOWORD(l)       ((ushort)(ulong)(l))
  32. #define HIWORD(l)       ((ushort)(((ulong)(l) >> 16) & 0xffff))
  33. #define LOBYTE(w)       ((uchar)(w))
  34. #define HIBYTE(w)       (((ushort)(w) >> 8) & 0xff)
  35.  
  36.     /* Addressing modes */
  37. #define    ADDR_HSG    0
  38. #define    ADDR_RED    1
  39.  
  40.     /* Device driver commands */
  41. #define    DEVRDIOCTL     3    /* IOCTL read            */
  42. #define    DEVPLAY        132    /* Device Play            */
  43. #define    DEVSTOP        133    /* Stop device play        */
  44.  
  45.     /* CDROM Device IOCTL commands */
  46. #define    IOI_ret_addr        0
  47. #define    IOI_loc_head        1
  48. #define    IOI_io_query        2
  49. #define    IOI_err_stats        3
  50. #define    IOI_audio_info        4
  51. #define    IOI_rd_drv_bytes    5
  52. #define    IOI_dev_status        6
  53. #define    IOI_ret_sectsize    7
  54. #define    IOI_ret_volsize        8
  55. #define    IOI_media_changed    9
  56. #define    IOI_audio_diskinfo    10
  57. #define    IOI_audio_trackinfo    11
  58. #define    IOI_audio_qchaninfo    12
  59. #define    IOI_audio_subinfo    13
  60. #define    IOI_upc_code        14
  61. #define    IOI_cmd_max        14
  62.  
  63. #define    IOO_eject_disc        0
  64. #define    IOO_lock_door        1
  65. #define    IOO_reset_drv        2
  66. #define    IOO_set_audio_param    3
  67. #define    IOO_wr_drv_bytes    4
  68. #define    IOO_cmd_max        4
  69.  
  70. #define    error_drive_not_ready    21
  71.  
  72. typedef    unsigned char    uchar;
  73. typedef    unsigned short    ushort;
  74. typedef    unsigned int    uint;
  75. typedef    unsigned long    ulong;
  76.  
  77.     /* Device driver request header */
  78. typedef struct Request_Hdr {
  79.     uchar        rqh_len;
  80.     uchar        rqh_unit;
  81.     uchar        rqh_cmd;
  82.     ushort        rqh_status;
  83.     uchar        rqh_rsvd[8];
  84.     } Request_Hdr;
  85.  
  86.     /* Request header for INIT function */
  87. typedef struct Init_Hdr {
  88.     Request_Hdr    init_rqh;
  89.     uchar        init_units;
  90.     uchar    far    *init_endaddr;
  91.     uchar    far    *init_bpbarr;
  92.     uchar        init_devno;
  93.     } Init_Hdr;
  94.  
  95.     /* Request header for IOCTL command */
  96. typedef struct Ioctl_Hdr {
  97.     Request_Hdr    ioctl_rqh;
  98.     uchar        ioctl_media;
  99.     uchar far    *ioctl_xfer;
  100.     ushort        ioctl_nbytes;
  101.     ushort        ioctl_sector;
  102.     uchar far    *ioctl_volid;
  103.     } Ioctl_Hdr;
  104.  
  105.     /* Request header for Read/Write command */
  106. typedef struct ReadWriteL_Hdr {
  107.     Request_Hdr    rwl_rqh;
  108.     uchar        rwl_addrmd;
  109.     uchar far    *rwl_xfer;
  110.     ushort        rwl_nsects;
  111.     ulong        rwl_sectno;
  112.     uchar        rwl_mode;
  113.     uchar        rwl_ilsize;
  114.     uchar        rwl_ilskip;
  115.     ushort        rwl_reqno;
  116.     } ReadWriteL_Hdr;
  117.  
  118.     /* Request header for Play Audio command */
  119. typedef struct PlayReq_Hdr {
  120.     Request_Hdr    pl_rqh;
  121.     uchar        pl_addrmd;
  122.     ulong        pl_start;
  123.     ulong        pl_num;
  124.     } PlayReq_Hdr;
  125.  
  126.     /* Record for audio_diskinfo IOCTL call */
  127. typedef struct DiskInfo_Rec {
  128.     uchar    cmd_code;
  129.     uchar    lo_tno;
  130.     uchar    hi_tno;
  131.     ulong    lead_out;
  132.     } DiskInfo_Rec;
  133.  
  134.     /* Record for UPC code IOCTL call */
  135. typedef struct UPCCode_Rec {
  136.     uchar    cmd_code;
  137.     uchar    ctrl_adr;
  138.     uchar    upc[7];
  139.     uchar    zero;
  140.     uchar    aframe;
  141.     } UPCCode_Rec;
  142.  
  143.     /* Record for audio_trackinfo IOCTL call */
  144. typedef struct TnoInfo_Rec {
  145.     uchar    cmd_code;
  146.     uchar    tno;
  147.     ulong    start_addr;
  148.     uchar    ctrl;
  149.     } TnoInfo_Rec;
  150.  
  151.     /* Record for audio_qchaninfo IOCTL call */
  152. typedef struct QchanInfo_Rec {
  153.     uchar    cmd_code;
  154.     uchar    ctrl;
  155.     uchar    tno;
  156.     uchar    x;
  157.     uchar    min;
  158.     uchar    sec;
  159.     uchar    frame;
  160.     uchar    zero;
  161.     uchar    pmin;
  162.     uchar    psec;
  163.     uchar    pframe;
  164.     } QchanInfo_Rec;
  165.  
  166.     /* Format for CDROM device driver header at CS:0 of device driver
  167.     ** Slightly extended from standard MSDOS character device driver
  168.     */
  169. typedef struct Dev_Hdr {
  170.     struct Dev_Hdr far *sdevnext;    /* Pointer to next dev header */
  171.     ushort      sdevatt;        /* Attributes of the device   */
  172.     void      (*sdevstrat)();    /* Strategy entry point       */
  173.     void      (*sdevint)();        /* Interrupt entry point      */
  174.     uchar      sdevname[8];        /* Name of device (only first byte used for block) */
  175.     ushort      sdevrsvd;        /* Reserved word          */
  176.     uchar      sdevlet;        /* Drive letter of first unit */
  177.     uchar      sdevunits;        /* Number of units handled    */
  178.     } Dev_Hdr;
  179.  
  180.     /* Record format for information returned with Get CDROM drive 
  181.     ** letter device list function request
  182.     */
  183. typedef struct Dev_List {
  184.     uchar        sub_unit;
  185.     Dev_Hdr far    *dev_addr;
  186.     } Dev_List;
  187.  
  188.     /* We use 100 tracks as there are 1-99 tracks on disk and we
  189.     ** use the 100th one to store the location of the lead-out
  190.     ** track.
  191.     */
  192. TnoInfo_Rec    TnoInfo[99 + 1];    /* Table of info for all tracks    */
  193. DiskInfo_Rec    DiskInfo;        /* Disk information for CD    */
  194. UPCCode_Rec    UPCCode;        /* UPC code for CD        */
  195. QchanInfo_Rec    QInfo;            /* Space for returned qchan info*/
  196. Dev_List    Dev_Tbl[26];        /* Table for all device drivers    */
  197. ushort        Num_Drives;        /* Number of CDROM drives    */
  198. ushort        First_DrvLetter;    /* First letter used by CDROM's    */
  199. PlayReq_Hdr    Play_Rec;        /* Record for Play requests    */
  200. Ioctl_Hdr    Ioctl_Rec;        /* Record for IOCTL requests    */
  201.  
  202.     /* External masm routine that sets up request to be passed to
  203.     ** the device driver.
  204.     */
  205. extern void    send_req(ReadWriteL_Hdr far *, Dev_Hdr far *);
  206.  
  207. /*
  208. ** Other declarations to make C 6.0 -W2 happy
  209. */
  210.  
  211. extern void dsp_addr(unsigned long addr);
  212. extern void dsp_ctrl(unsigned char c);
  213. extern unsigned char bcd2bin(unsigned char c);
  214. extern unsigned long red2hsg(unsigned long l);
  215. extern unsigned int play(struct Dev_List *drv,unsigned long start,
  216.     unsigned long num, unsigned char mode);
  217. extern unsigned int ioctl(struct Dev_List *drv,unsigned char *xbuf,
  218.     unsigned char cmd, unsigned char cmdlen);
  219. extern void read_toc(struct Dev_List *drv);
  220. extern void play_tracks(struct Dev_List *drv);
  221. extern void find_drivers(void);
  222. extern void main(void);
  223.  
  224. /* dsp_addr() -
  225. **
  226. ** DESCRIPTION
  227. **    Prints red book address in MM:SS:FF Min/Sec/Frame format
  228. */
  229. void dsp_addr(addr)
  230. ulong    addr;
  231.     {
  232.     printf("%2d:",    HIWORD(addr) & 0xff);
  233.     printf("%02d.",    LOWORD(addr) >> 8);
  234.     printf("%02d",    LOWORD(addr) & 0xff);
  235.     }
  236.  
  237. /* dsp_ctrl() -
  238. **
  239. ** DESCRIPTION
  240. **    Prints what attributes are in the 4 bits of track control information.
  241. */
  242. void dsp_ctrl(c)
  243. uchar    c;
  244.     {
  245.     printf("    ");
  246.     switch (c & 0xc0) {
  247.         case 0x00:
  248.         case 0x80:
  249.             if (c & 0x80)
  250.                 printf("4 chnl ");
  251.             else
  252.                 printf("2 chnl ");
  253.             if (c & 0x10)
  254.                 printf("w/ pre-emphasis");
  255.             else
  256.                 printf("w/o pre-emphasis");
  257.             break;
  258.         case 0x40:
  259.             if (c & 0x10)
  260.                 printf("reserved");
  261.             else
  262.                 printf("data");
  263.             break;
  264.         case 0xc0:
  265.             printf("reserved");
  266.             break;
  267.         }
  268.     if (c & 0x20)
  269.         printf(" COPY\n");
  270.     else
  271.         printf(" !COPY\n");
  272.     }
  273.  
  274. /* bcd2bin() -
  275. **
  276. ** DESCRIPTION
  277. **    Converts BCD to binary. BCD breaks a byte into two 4-bit
  278. **    nibbles where each ranges from 0-9. BCD can represent 0-99
  279. **    whereas binary does 0-255. If the value passed in is an
  280. **    illegal BCD value, we return 0xff
  281. */
  282. uchar bcd2bin(c)
  283. uchar    c;
  284.     {
  285.     if ((c & 0x0f) > 0x09)
  286.         return(0xff);
  287.     if ((c & 0xf0) > 0x90)
  288.         return(0xff);
  289.     return((((c & 0xf0) >> 4) * 10) + (c & 0x0f));
  290.     }
  291.  
  292. /* red2hsg() -
  293. **
  294. ** DESCRIPTION
  295. **    Converts a binary red book address to high sierra addressing
  296. **    The msb of the red book is always zero, the next less significant
  297. **    byte is the minute (0-59+), then second (0-59) and lsb is the
  298. **    frame (0-75). The conversion is
  299. **        hsg = min * 60 * 75 + sec * 75 + frame;
  300. */
  301. ulong red2hsg(l)
  302. ulong    l;
  303.     {
  304.     return((ulong) (HIWORD(l) & 0xff) * 60 * 75 +
  305.         (ulong) (LOWORD(l) >> 8) * 75 +
  306.         (ulong) (LOWORD(l) & 0xff));
  307.     }
  308.  
  309.  
  310. /* play() -
  311. **
  312. ** DESCRIPTION
  313. **    Sends the request to play num frames at address start on
  314. **    drive drv. Mode determines whether the starting address is to
  315. **    be interpreted as high sierra or red book addressing.
  316. **    If num == 0, then instead of sending a PLAY-AUDIO command,
  317. **    we issue a STOP-AUDIO command.
  318. */
  319. uint play(drv, start, num, mode)
  320. Dev_List    *drv;
  321. ulong        start;
  322. ulong        num;
  323. uchar        mode;
  324.     {
  325.     register PlayReq_Hdr    *req = &Play_Rec;
  326.  
  327.     printf("Drive %8Fs", drv->dev_addr->sdevname);
  328.     printf(" unit %d", drv->sub_unit);
  329.     if (mode == ADDR_HSG)
  330.         printf(" start %ld num %ld\n", start, num);
  331.     else {
  332.         printf(" start ");
  333.         dsp_addr(start);
  334.         printf(" num %ld\n", num);
  335.         }
  336.  
  337.     req->pl_rqh.rqh_len    = sizeof(PlayReq_Hdr);
  338.     req->pl_rqh.rqh_unit    = drv->sub_unit;
  339.     req->pl_rqh.rqh_cmd    = (uchar) (num ? DEVPLAY : DEVSTOP);
  340.     req->pl_rqh.rqh_status    = 0;
  341.  
  342.     req->pl_addrmd    = mode;
  343.     req->pl_start    = start;
  344.     req->pl_num    = num;
  345.  
  346.     send_req((ReadWriteL_Hdr far *) req, drv->dev_addr);
  347.     if (req->pl_rqh.rqh_status & 0x8000) {
  348.         printf("    Error on play - status = 0x%r\n", req->pl_rqh.rqh_status, 0);
  349.         return(error_drive_not_ready);
  350.         }
  351.  
  352.     return(0);
  353.     }
  354.  
  355. /* ioctl() -
  356. **
  357. ** DESCRIPTION
  358. **    Sends an IOCTL request to the device driver in drv. The
  359. **    ioctl command is cmd and the ioctl cmd length is cmdlen.
  360. **    The buffer for the command is pointed to by xbuf.
  361. */
  362. uint ioctl(drv, xbuf, cmd, cmdlen)
  363. Dev_List    *drv;
  364. uchar        *xbuf;
  365. uchar        cmd;
  366. uchar        cmdlen;
  367.     {
  368.     register Ioctl_Hdr    *io = &Ioctl_Rec;
  369.  
  370.     io->ioctl_rqh.rqh_len    = sizeof(Ioctl_Hdr);
  371.     io->ioctl_rqh.rqh_unit    = drv->sub_unit;
  372.     io->ioctl_rqh.rqh_cmd    = DEVRDIOCTL;
  373.     io->ioctl_rqh.rqh_status = 0;
  374.  
  375.     io->ioctl_media        = 0;
  376.     io->ioctl_xfer        = (char far *) xbuf;
  377.     *xbuf            = cmd;
  378.     io->ioctl_nbytes    = cmdlen;
  379.     io->ioctl_sector    = 0;
  380.     io->ioctl_volid        = 0;
  381.  
  382.     send_req((ReadWriteL_Hdr far *) io, drv->dev_addr);
  383.     if (io->ioctl_rqh.rqh_status & 0x8000) {
  384.         printf("    Error on play - status = 0x%r\n", io->ioctl_rqh.rqh_status);
  385.         return(error_drive_not_ready);
  386.         }
  387.     return(0);
  388.     }
  389.  
  390. /* read_toc() -
  391. **
  392. ** DESCRIPTION
  393. **    Reads the disk information from the TOC in the qchannel
  394. **    to find the first and last track numbers and builds a
  395. **    table (TnoInfo) of the starting address for each track by 
  396. **    asking the device driver for the info for each track.
  397. */
  398. void read_toc(drv)
  399. Dev_List    *drv;
  400.     {
  401.     TnoInfo_Rec    *t;
  402.     QchanInfo_Rec    *q = &QInfo;
  403.     uchar        *s;
  404.     uint        i;
  405.  
  406.     if (ioctl(drv,(uchar *) &DiskInfo, IOI_audio_diskinfo,
  407.         sizeof(DiskInfo_Rec)))
  408.         printf("    Failed to read TOC\n");
  409.  
  410.     printf("DiskInfo.lo_tno %d ", DiskInfo.lo_tno);
  411.     printf("DiskInfo.hi_tno %d ", DiskInfo.hi_tno);
  412.     printf("DiskInfo.lead_out ");
  413.     dsp_addr(DiskInfo.lead_out);
  414.     printf("\n");
  415.  
  416.     if (ioctl(drv,(uchar *) &UPCCode, IOI_upc_code, sizeof(UPCCode_Rec)))
  417.         printf("    Failed to find UPC code\n");
  418.  
  419.     printf("UPC code: ");
  420.     if (UPCCode.ctrl_adr == 0)
  421.         printf("Failed to find UPC code\n");
  422.     else {
  423.         s = UPCCode.upc;
  424.         for (i = 0; i < 7; i++) {
  425.             printf("%02d.%02d.", (*s & 0xf0) >> 4, *s & 0x0f);
  426.             s++;
  427.             }
  428.         printf(" zero = %d", UPCCode.zero);
  429.         printf(" aframe = %d\n", bcd2bin(UPCCode.aframe));
  430.         }
  431.  
  432.         /* The entry after the last track has as it's
  433.         ** starting address the beginning of the lead-out
  434.         ** track which is the end of the audio on the disc.
  435.         ** This is why we have 99+1 records in TnoInfo
  436.         */
  437.     TnoInfo[DiskInfo.hi_tno + 1].start_addr = DiskInfo.lead_out;
  438.  
  439.     t = &TnoInfo[DiskInfo.lo_tno];
  440.     for (i = DiskInfo.lo_tno; i <= DiskInfo.hi_tno; i++) {
  441.         t->tno = (uchar) i;
  442.         ioctl(drv,(uchar *) t, IOI_audio_trackinfo, sizeof(TnoInfo_Rec));
  443.         printf("tno %2d ", t->tno);
  444.         printf("start_addr ");
  445.         dsp_addr(t->start_addr);
  446.         printf(" ctrl 0x%02x ", t->ctrl);
  447.         dsp_ctrl(t->ctrl);
  448.         t++;
  449.         }
  450.     }
  451.  
  452. /* play_tracks() -
  453. **
  454. ** DESCRIPTION
  455. **    If WHOLE is defined, then we play the entire disc.
  456. **    Otherwise we play about 10 seconds (the amount of time
  457. **    to do 200 qchannel queries) from each track on the
  458. **    disk
  459. */
  460. void play_tracks(drv)
  461. Dev_List    *drv;
  462.     {
  463.     TnoInfo_Rec    *t;
  464.     QchanInfo_Rec    *q = &QInfo;
  465.     uint        i;
  466.     uint        j;
  467.     uint        k;
  468.     ulong        num;
  469.     uchar        *s;
  470.  
  471.     t = &TnoInfo[DiskInfo.lo_tno];
  472.  
  473. #ifdef WHOLE
  474.     num = red2hsg(DiskInfo.lead_out) - red2hsg(t->start_addr);
  475.     play(drv, t->start_addr, num, ADDR_RED);
  476. #else
  477.         /* Play each track on disc */
  478.     for (i = DiskInfo.lo_tno; i <= DiskInfo.hi_tno; i++) {
  479.             /* Calc number of frames to play */
  480.         num = red2hsg(TnoInfo[i + 1].start_addr) - red2hsg(TnoInfo[i].start_addr);
  481.             /* Begin playing audio for this track */
  482.         play(drv, t->start_addr, num, ADDR_RED);
  483.         t++;
  484.             /* Loop 200 times and report the qchannel status */
  485.         j = 0;
  486.         do {
  487.             ioctl(drv, q, IOI_audio_qchaninfo, sizeof(QchanInfo_Rec));
  488.             printf("ctrl 0x%02x ",     q->ctrl);
  489.             if ((q->ctrl & 0x0f) == 3) {
  490.                 printf("ISRC code: display Nimp.\n");
  491.                 }
  492.             else if ((q->ctrl & 0x0f) == 2) {
  493.                 printf("UPC code: ");
  494.                 s = &q->tno;
  495.                 for (k = 0; k < 8; k++) {
  496.                     printf("%02d.%02d.", (*s & 0xf0) >> 4, *s & 0x0f);
  497.                     s++;
  498.                     }
  499.                 
  500.                 printf("frame = %2d\n", bcd2bin(q->pframe));
  501.                 }
  502.             else {
  503.                 printf("tno %2d ",     bcd2bin(q->tno));
  504.                 printf("x %2d ",     bcd2bin(q->x));
  505.                 printf("%2d:%02d.%02d",  q->min, q->sec, q->frame);
  506.                 printf(" zero = 0x%02x", q->zero);
  507.                 printf(" %2d:%02d.%02d", q->pmin, q->psec, q->pframe);
  508.                 printf("\n");
  509.                 }
  510. #ifdef TRACK_BY_TRACK
  511.     /*    NOTE: This is what I'd like to have except that when seeking
  512.     **    to the beginning of the audio track, the qchannel status
  513.     **    has apparently random entries and also as it homes in on the
  514.     **    track, it begins reporting its position before it's actually
  515.     **    hit the frame we want. Sometimes these entries are in the 
  516.     **    previous or next track so that this test fails and we move 
  517.     **    on before we've even begun to play the audio we asked to 
  518.     **    seek to.
  519.     */
  520.             disc_time = (ulong) q->min << 16 +
  521.                     (ulong) q->sec << 8 +
  522.                     (ulong) q->frame;
  523.             } while (q->tno == 0 || (red2hsg(disc_time) < red2hsg(t->start_addr)));
  524. #else
  525.             } while (j++ < 100);
  526. #endif
  527.             /* Stop the audio */
  528.         play(drv, 0L, 0L, ADDR_RED);
  529.         }
  530. #endif
  531.     }
  532.  
  533. /* find_drivers() -
  534. **
  535. **    Using INT 2Fh with AH=15h (the MSCDEX function request interface)
  536. **    we can ask MSCDEX for the number and location of all CDROM
  537. **    device drivers on the system.
  538. **
  539. **    Unfortunately at present, if MSCDEX is not present, the carry
  540. **    flag is not set when an INT 2Fh is issued with AH=15h...I'll 
  541. **    figure out what's going on and figure out a good way to tell
  542. **    if MSCDEX is present or not.
  543. */
  544. void find_drivers()
  545.     {
  546.     union REGS    inregs;
  547.     union REGS    outregs;
  548.     struct SREGS    segregs;
  549.     uchar far    *d = (uchar far *) Dev_Tbl;
  550.     Dev_Hdr far    *sdev;
  551.     uint        i;
  552.  
  553.     inregs.x.ax = 0x1500;    /* Get Number of CDROM drive letters    */
  554.     inregs.x.bx = 0;    /* Init to zero                */
  555.     int86(0x2f, &inregs, &outregs);
  556.  
  557.         /* If number of drives returned is still 0, then MSCDEX
  558.         ** is not installed.
  559.         */
  560.     if (outregs.x.bx == 0) {
  561.         printf("MSCDEX not installed\n");
  562.         exit(1);
  563.         }
  564.  
  565.     Num_Drives    = outregs.x.bx;
  566.     First_DrvLetter    = outregs.x.cx;
  567.     printf("There are %d drives starting at %c:\n", Num_Drives, First_DrvLetter + 'A');
  568.  
  569.     inregs.x.ax = 0x1501;    /* Get CDROM drive letter device list    */
  570.     inregs.x.bx  = LOWORD(d);
  571.     segregs.es = HIWORD(d);
  572.     int86x(0x2f, &inregs, &outregs, &segregs);
  573.  
  574.         /* Check if carry set        */
  575.     if (outregs.x.cflag) {
  576.         printf("MSCDEX not present\n");
  577.         exit(1);
  578.         }
  579.  
  580.     for (i = 0; i < Num_Drives; i++) {
  581.         printf("Drive %d unit %d addr 0x%lx ", i, Dev_Tbl[i].sub_unit, Dev_Tbl[i].dev_addr); 
  582.         sdev = Dev_Tbl[i].dev_addr; 
  583.         printf("'%8Fs'\n", sdev->sdevname);
  584.         }
  585.     }
  586.  
  587. void main()
  588.     {
  589.     find_drivers();
  590.     read_toc(&Dev_Tbl[0]);
  591.     play_tracks(&Dev_Tbl[0]);
  592.     }
  593.